/*=======================================================
Critical Links
Auto Configuration Server

Authors:
	Rui Eduardo Gouveia Gil - r-gil@critical-links.com

Date: 2010
Version 0.1
=======================================================*/
#include <pion/net/HTTPResponseWriter.hpp>
#include <pion/net/HTTPTypes.hpp>
#include "httpd/HttpUserManager.h"
#include "httpd/HttpFileUpload.h"
#include "database/DataBase.h"
#include "ACSConfig.h"

namespace acs
{

rgcpp::Sequence<int> HttpFileUpload::fileID(1);
std::string HttpFileUpload::TMP_FILE_PEFIX="/tmp/eacs_upload_";

HttpFileUpload::HttpFileUpload()
{

}

HttpFileUpload::~HttpFileUpload()
{

}
void HttpFileUpload::operator()(pion::net::HTTPRequestPtr& request, pion::net::TCPConnectionPtr& tcpConn)
{
	HttpUserThreadBind httpUserThreadBind(request->getUser());

	if(request->getMethod().compare("POST") == 0)
	{
		std::string redirectUrl;

		try
		{
			int id;
			int type;
			pion::net::HTTPTypes::QueryParams& queryParams = request->getQueryParams();
			for(pion::net::HTTPTypes::QueryParams::iterator itr = queryParams.begin(); itr != queryParams.end(); itr++)
			{
				if(itr->first.compare("type") == 0)
					type = atoi(itr->second.c_str());
				else if(itr->first.compare("id") == 0)
					id = atoi(itr->second.c_str());
			}

			if(type == 1)
			{
				redirectUrl = "/configurations.html#show#";
				redirectUrl += rgcpp::ToString(id);
			}
			else if(type == 2)
			{
				redirectUrl = "/devices.html#show#";
				redirectUrl += rgcpp::ToString(id);
			}
			else
				redirectUrl="/index.html";

			std::string tempFile = HttpFileUpload::TMP_FILE_PEFIX+rgcpp::ToString(HttpFileUpload::fileID.getNextSequenceId());

			{std::ofstream stream(tempFile.c_str(),std::ios_base::binary | std::ios_base::trunc | std::ios_base::out);

			if(!stream.good())
			{
				Throw(rgcpp::RuntimeException("Invalid temporary file."));
			}

			stream.write(request->getContent(),request->getContentLength());

			stream.close();}

			try
			{
				std::string fileName;
				this->parseContent(fileName,tempFile);

				rgcpp::Types<std::string,std::string,std::string,std::string,std::string,std::string,std::string> backupInformation = this->getBackupInformation(tempFile);

				if(type == 1)
				{
					if(DataBase::getInstance()->existsConfiguration(id))
					{
						std::string destFile = ACSConfig::getDataDirectory()+std::string("Configuration_BackupRestore_")+rgcpp::ToString(id);

						Configuration configuration = DataBase::getInstance()->getConfiguration(id);
						std::string restoreOptions = rgcpp::ToString(configuration.isRWan())+"-"+rgcpp::ToString(configuration.isRUsers());
						configuration.setSHA(this->generateBackupSHA(tempFile,restoreOptions));
						configuration.setConfigFileName(fileName);
						configuration.setCFLicenseID(backupInformation.t1);
						configuration.setCFRelease(backupInformation.t2);
						configuration.setCFVoipSounds(backupInformation.t3);
						configuration.setCFType(backupInformation.t4);
						configuration.setCFCreation(backupInformation.t5);
						configuration.setCFDescription(backupInformation.t6);
						configuration.setCFReboot(backupInformation.t7);

						rgcpp::FileLock fileLock(ACSConfig::getDataDirectory()+std::string("Configuration_BackupRestore_")+rgcpp::ToString(id)+".lock");

						if(boost::filesystem::exists(destFile))
							boost::filesystem::remove(destFile);
						boost::filesystem::copy_file(tempFile,destFile);

						DataBase::getInstance()->updateConfiguration(configuration);
						DataBase::getInstance()->setStatusDevicesWithConfiguration(id,DS_PENDING_TASKS);
					}
				}
			}
			catch(rgcpp::Exception& e)
			{
				LOG_WARN_STR("Empty file...");
				throw e;
			}

			pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));
			writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_OK);
			writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_OK);
			writer->getResponse().setContentType("text/html");
			writer->write("{result:'1'}");
			writer->write(pion::net::HTTPTypes::STRING_CRLF);
			writer->write(pion::net::HTTPTypes::STRING_CRLF);
			writer->send();
		}
		catch(rgcpp::Exception& e)
		{
			pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));
			writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_SERVER_ERROR);
			writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_SERVER_ERROR);
			writer->getResponse().setContentType("text/html");
			writer->write("{result:'0'}");
			writer->write(pion::net::HTTPTypes::STRING_CRLF);
			writer->write(pion::net::HTTPTypes::STRING_CRLF);
			writer->send();
		}
	}
	else
	{
		pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));
		writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_BAD_REQUEST);
		writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_BAD_REQUEST);
		writer->getResponse().addHeader(pion::net::HTTPTypes::HEADER_LOCATION,"/index.html");
		writer->send();
	}
}

void HttpFileUpload::parseContent(std::string& file, std::string& tempFile)
{
	std::ifstream stream(tempFile.c_str(),std::ios_base::binary | std::ios_base::in);

	if(!stream.good())
	{
		Throw(rgcpp::RuntimeException("Invalid temporary file."));
	}

	std::string delemiter;
	getline(stream,delemiter);
	delemiter.resize(delemiter.size()-1);

	while(!stream.eof())
	{
		rgcpp::Types<std::string,std::string,std::string> parameter = HttpFileUpload::getParameter(stream,delemiter);

		if(parameter.t1.compare("upfile") == 0)
		{
			file = parameter.t2;
			tempFile = parameter.t3;
		}
	}

	stream.close();

	if(file.empty())
	{
		Throw(rgcpp::IllegalArgumentException("Invalid File..."));
	}
}

rgcpp::Types<std::string,std::string,std::string> HttpFileUpload::getParameter(std::ifstream& stream, std::string delemiter)
{
	rgcpp::Types<std::string,std::string,std::string> parameter;

	bool file=false;
	std::string line;
	while(!stream.eof())
	{
		getline(stream,line);

		if(line.find("Content-Disposition:") != std::string::npos)
		{
			size_t pos = line.find("name=\"");
			line = line.substr(pos+6);
			pos = line.find("\"");

			parameter.t1 = line.substr(0,pos);
			line = line.substr(pos+1);

			pos = line.find("filename=\"");
			if(pos != std::string::npos)
			{
				file = true;
				line = line.substr(pos+10);
				pos = line.find("\"");

				parameter.t2 = line.substr(0,pos);
			}
		}

		if(line.compare("\r") == 0)
			break;
	}

	getline(stream,line);

	if(file)
	{
		parameter.t3 = HttpFileUpload::TMP_FILE_PEFIX+rgcpp::ToString(HttpFileUpload::fileID.getNextSequenceId());

		std::ofstream streamOut(parameter.t3.c_str(),std::ios_base::binary | std::ios_base::trunc | std::ios_base::out);

		if(!streamOut.good())
		{
			Throw(rgcpp::RuntimeException("Invalid temporary file."));
		}

		streamOut.write(line.c_str(),line.size());
		streamOut.write("\n",1);

		bool last=false;
		while(!stream.eof())
		{
			getline(stream,line);

			if(line.compare(delemiter+"\r") == 0 || line.compare(delemiter+"--\r") == 0)
				break;

			if(last)
			{
				streamOut.write("\r\n",2);
				last=false;
			}

			if(!line.empty() && (line.at(line.size()-1) == '\r'))
			{
				line.resize(line.size()-1);
				last = true;
			}

			streamOut.write(line.c_str(),line.size());

			if(!last)
				streamOut.write("\n",1);
		}

		streamOut.close();
	}
	else
	{
		while(!stream.eof())
		{
			getline(stream,line);

			if(line.compare(delemiter+"\r") == 0 || line.compare(delemiter+"--\r") == 0)
				break;

			parameter.t2 += line;
			parameter.t2 += "\n";
		}

		if(!parameter.t2.empty())
			parameter.t2.resize(parameter.t2.size()-2);
	}

	return parameter;
}

std::string HttpFileUpload::generateBackupSHA(const std::string& filePath, const std::string& restoreOptions)
{
	std::ifstream file(filePath.c_str(),std::ios_base::in);

	if(!file.good())
	{
		Throw(rgcpp::IllegalArgumentException(std::string("Invalid restore file: ")+filePath));
	}

	file.seekg(0,std::ios::end);
	unsigned int size=file.tellg();
	file.seekg(0,std::ios::beg);

	char buffer[16384];
	char digest[SHA_DIGEST_SIZE];
	rgcpp::SHAGenerator shaGenerator;

	shaGenerator.init();

	while(size)
	{
		if(size < 16384)
		{
			file.read(buffer,size);
			shaGenerator.update(buffer,size);
			size = 0;
		}
		else
		{
			file.read(buffer,16384);
			shaGenerator.update(buffer,16384);
			size -= 16384;
		}
	}

	shaGenerator.update(restoreOptions.c_str(),restoreOptions.size());
	shaGenerator.digest(digest);

	return rgcpp::ToHex(digest,SHA_DIGEST_SIZE);
}

rgcpp::Types<std::string,std::string,std::string,std::string,std::string,std::string,std::string> HttpFileUpload::getBackupInformation(std::string filePath)
{
	rgcpp::Types<std::string,std::string,std::string,std::string,std::string,std::string,std::string> result;

	rgcpp::ZipFile zipFile(filePath);

	std::string fileControl;
	std::string extControl = ".control";
	std::vector<std::string> files = zipFile.listArchive();
	for(std::vector<std::string>::iterator i=files.begin(); i!=files.end(); i++)
	{
		if(i->size() > extControl.size() && i->substr(i->size()-extControl.size()).compare(extControl) == 0)
		{
			fileControl = *i;
			break;
		}
	}

	if(fileControl.empty())
	{
		Throw(rgcpp::RuntimeException("Invalid Backup Restore File..."));
	}

	std::string tempFile = HttpFileUpload::TMP_FILE_PEFIX+rgcpp::ToString(HttpFileUpload::fileID.getNextSequenceId());

	zipFile.extractFileToFile(fileControl,tempFile);

	TiXmlDocument xmlDocument;
	xmlDocument.LoadFile(tempFile.c_str());

	TiXmlElement* backup = xmlDocument.FirstChildElement("Backup");
	if(backup == NULL)
	{
		Throw(rgcpp::IllegalArgumentException(std::string("Invalid xml: element <Backup> missing.")));
	}

	TiXmlElement* archive = backup->FirstChildElement("Archive");
	if(archive == NULL)
	{
		Throw(rgcpp::IllegalArgumentException(std::string("Invalid xml: element <Archive> missing.")));
	}

	if(backup->Attribute("licenseid"))
		result.t1 = backup->Attribute("licenseid");

	if(backup->Attribute("release"))
		result.t2 = backup->Attribute("release");

	if(backup->Attribute("voipsounds"))
		result.t3 = backup->Attribute("voipsounds");

	if(backup->Attribute("type"))
		result.t4 = backup->Attribute("type");

	if(backup->Attribute("ended"))
		result.t5 = backup->Attribute("ended");

	if(archive->Attribute("description"))
		result.t6 = archive->Attribute("description");

	if(archive->Attribute("reboot"))
		result.t7 = archive->Attribute("reboot");

	return result;
}

}

extern "C" PION_SERVICE_API acs::HttpFileUpload* pion_create_HttpFileUpload(void)
{
	return new acs::HttpFileUpload();
}

extern "C" PION_SERVICE_API void pion_destroy_HttpFileUpload(acs::HttpFileUpload *service_ptr)
{
	delete service_ptr;
}
